/*
	ecPhaseTwo.c
		-Modified Version of echoClient.c-
	
	This program tests responses:
			GET
			HEAD
			POST
			OPTIONS
			DELETE

	Methods PUT, TRACE, CONNECT, WILL BE DEALT WITH LATER

*/

/* 	WARNING - ORDER IMPORTANT FOR INCLUDES!    */
/*	sys/types.h needs to be before sys/socket.h! */

#include <sys/types.h>	//prerequisite typedefs
#include <sys/socket.h>	// - def's of structures needed for sockets
#include <stdio.h>		
#include <unistd.h>		
#include <errno.h>		//names for error numbers
#include <netinet/in.h>	//header file in.h used for internet domain addresses
#include <arpa/inet.h>	//utility function prototypes
#include <stdlib.h>
#include <strings.h>	//strings.h needed for bzero... 
#include <string.h>
#include <netdb.h>
#include <fcntl.h>//network info lookup - prototypes and structs
#define BUFSIZE  8192
extern int errno;
const int MAXECHOLEN=256;

void PushFileDownSocket(int fd, int socket);
void ReceiveResponse(int socket);

void PushFileDownSocket(int fd, int socket)
{
    char buffer[BUFSIZE];
    size_t bytesToWrite = lseek(fd,0,SEEK_END);
    lseek(fd,0,SEEK_SET);
    while(bytesToWrite > 0)
    {
        if(bytesToWrite > BUFSIZE)
        {
            read(fd,buffer,BUFSIZE);
            write(socket,buffer,BUFSIZE);
        }
        else
        {
            read(fd,buffer,bytesToWrite);
            write(socket,buffer,bytesToWrite);
        }
    }
}
void ReceiveResponse(int socket)
{
    char buffer[BUFSIZE];
    int msgLength;
    msgLength = read(socket,buffer,BUFSIZE);
    while(msgLength > 0)
    {
        write(1,buffer,msgLength);
        msgLength = read(socket,buffer,BUFSIZE);
    }
}

int main (int argCount, char *argValues[]) 
{
    int socketDescriptor;
    
    /* 	sockaddr_in is a structure containing an internet address, 
    		#include netinet/in.h   used on line 94ish*/
    struct sockaddr_in serverAddress;
    
    /*	The hints argument points to an addrinfo structure that specifies  cri‐
       teria  for selecting the socket address structures returned in the list
       pointed to by res.  If hints is not  NULL  it  points  to  an  addrinfo
       structure  whose ai_family, ai_socktype, and ai_protocol specify crite‐
       ria that limit the set of socket addresses returned  by  getaddrinfo(),
	*/
    struct addrinfo hints, *res=NULL;
    char echoResponse[MAXECHOLEN];
    int status, i;
	char server[256], port[]="10000";

    if (argCount != 3) 
    {
		fprintf(stderr, "Usage: %s <server name> <test file>\n", argValues[0]);
		exit (-1);
    }
    strcpy(server, argValues[1]);
    
    /* getservbyname - returns a structure which specifies its name, protocol 
    				and its	port address */
    struct servent *entry = getservbyname("http", "tcp");
    
    /*  The  bzero()  function sets the first n bytes of the byte area starting
       at s to zero (bytes containing '\0').      */
    bzero(&hints, sizeof(hints));
    
    /* ai_flags field of addrinfo() struct specifies additional options, or 
    			flags that can be set , in this case we try both IPV4 and IPV6 
    			addresses. 		*/
    hints.ai_flags = AI_V4MAPPED | AI_ALL;
    
    /*	ai_family  specifies  the  desired address family for the returned 
    		addresses.  Valid values for  this  field  include AF_INET  and  
    		AF_INET6.  The value AF_UNSPEC indicates that  getaddrinfo()  should  
    		return socket addresses for any address family (either IPv4 or IPv6, 
    		for example) that can be used with node and service.
	*/
    hints.ai_family = AF_UNSPEC;
    
    /*	ai_socktype This field specifies the preferred socket type, for example
                   SOCK_STREAM  or  SOCK_DGRAM.   Specifying  0  in this field
                   indicates that socket addresses of any type can be returned
                   by getaddrinfo().
    */
    hints.ai_socktype = SOCK_STREAM;
    
    /*	inet_pton stores the IP address in sa - from printable (presentation)
    	  		to network - works with ntop (network to presentation)  */
    int retCode = inet_pton(AF_INET, server, &serverAddress);
    
    if (retCode == 1) 	// It was a valid ipv4 text address
    { 
		hints.ai_family = AF_INET;
    } 
    else 
    {
        retCode = inet_pton(AF_INET6, server, &serverAddress);
        if (retCode == 1) 		/* valid ipv6 address */
        { 
	    	hints.ai_family = AF_INET6;
		}
    }
    
    /* The following code helps us know what error is present (if any) */
    
   /*  getaddrinfo() - Given node and service, which identify an Internet host 
   		and a  service, returns  one  or more addrinfo structures, each of which
       	contains an Internet address that can be specified in a call to bind(2)
       	or  connect(2).                */
    retCode = getaddrinfo(server, port, &hints, &res);
    if (retCode != 0) 
    {
		switch (retCode) 
		{
	
		/* EAI_ADDRFAMILY
              The  specified  network host does not have any network addresses
              in the requested address family. */
		/*case EAI_ADDRFAMILY:
		printf("EAI_ADDRFAMILY error\n");
		break;
		*/
		
		 /*	EAI_AGAIN
              The name server returned a temporary  failure  indication.   Try
              again later.	*/
			case EAI_AGAIN:
			printf("EAI_AGAIN error\n");
			break;
		
		/*	EAI_BADFLAGS
              hints.ai_flags   contains   invalid  flags;  or,  hints.ai_flags
              included AI_CANONNAME and name was NULL.	*/
			case EAI_BADFLAGS:
			printf("EAI_BADFLAGS error\n");
			break;
		
		/*	EAI_FAIL
              The name server returned a permanent failure indication.      */
			case EAI_FAIL:
			printf("EAI_FAIL error\n");
			break;
		
			case EAI_FAMILY:
			printf("EAI_FAMILY error\n");
			break;
		
			case EAI_MEMORY:
			printf("EAI_MEMORY error\n");
			break;
		
		/*case EAI_NODATA:
		printf("EAI_NODATA error\n");
		break;
		*/
			case EAI_NONAME:
			printf("EAI_NONAME error\n");
			break;
		
			case EAI_SERVICE:
			printf("EAI_SERVICE error\n");
			break;
		
			case EAI_SOCKTYPE:
			printf("EAI_SOCKTYPE error\n");
			break;
		
			case EAI_SYSTEM:
			printf("EAI_SYSTEM error\n");
			break;
		
			default:
			printf("Unknown error -- code = %d\n", retCode);
			break;
		}
		
		printf("Host not found ---> %s\n", gai_strerror(retCode));
		
        if (retCode == EAI_SYSTEM) 
        {
	    	perror("getaddrinfo() failed");
		}
    }

		/*  socket() - creates a new socket and returns its descriptor   */
    socketDescriptor = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (socketDescriptor < 0) //echoServer localhost 10000
    {
        perror("Socket Creation Failed");
        exit(-1);
    }
    
    /* 	connect() - assigns a free local port number to a socket, for 
    	TCP sockets, it causes an attempt to establish a new TCP connection */
    status = connect (socketDescriptor, res->ai_addr, res->ai_addrlen);
    
    if (status != 0) 
    {
		perror("Connect Failed");
        exit(-1);
    }
    
    int fd = open(argValues[2],O_RDONLY);
    PushFileDownSocket(fd,socketDescriptor);
    ReceiveResponse(socketDescriptor);
    close(fd);
	
	/*  close() - "one-way" close of a socket descripter  */
    close(socketDescriptor);
    exit(0);
}
